package devcken.persistence.validation;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

import org.apache.commons.lang.WordUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Entity(영속성 도메인 클래스)의 Id로 지정된 필드에 대해 유효성 검증 인터페이스를 제공한다.
 * @author Devcken
 */
public class EntityIdValidator implements ConstraintValidator<EntityIdNotNull, Object>
{
	private final static Logger logger = LoggerFactory.getLogger(EntityIdValidator.class);
	
	/**
	 * {@link EntityIdValidator.isValid} 호출을 위해 Validator를 초기화한다.
	 */
	@Override
	public void initialize(EntityIdNotNull constraintAnnotation)
	{
		
	}

	/**
	 * 전달되는 영속성 도메인 인스턴스에 대해서 Id로 지정된 필드의 유효성을 검증한다.
	 */
	@Override
	public boolean isValid(Object value, ConstraintValidatorContext context)
	{
		if (value == null) return false;
		
		Class<? extends Object> targetClass = value.getClass();
		
		Entity entityAnnotation = targetClass.getAnnotation(Entity.class);
		
		// 영속성 도메인 클래스가 아닌 경우 실패 처리한다.
		if (entityAnnotation == null)
		{
			logger.warn("Class '%s' is not entity domain.", targetClass.getName());
			
			return false;
		}
		
		Field[] fields = targetClass.getDeclaredFields();
		
		for (Field field : fields)
		{
			String getterMethodName = String.format("get%s", WordUtils.capitalize(field.getName()));
			
			try
			{
				Method getterMethod = targetClass.getMethod(getterMethodName);
				
				Id getterIdAnnotation = getterMethod.getAnnotation(Id.class);
				
				if (getterIdAnnotation != null)
				{
					Object fieldValue = getterMethod.invoke(value);
					
					if (fieldValue == null)
					{
						return false;
					}
				}
				
				EmbeddedId getterEmbeddedId = getterMethod.getAnnotation(EmbeddedId.class);
				
				if (getterEmbeddedId != null)
				{
					Object embeddedIdInstance = getterMethod.invoke(value);
					
					Class<? extends Object> embeddedIdClass = embeddedIdInstance.getClass();
					
					Field[] embeddedIdFields = embeddedIdClass.getDeclaredFields();
					
					for (Field embeddedIdField : embeddedIdFields)
					{
						String embeddedIdGetterMethodName = String.format("get%s", WordUtils.capitalize(embeddedIdField.getName()));
						
						Method embeddedIdGetterMethod = embeddedIdClass.getMethod(embeddedIdGetterMethodName);
						
						Object embeddedIdFieldValue = embeddedIdGetterMethod.invoke(embeddedIdInstance);
						
						if (embeddedIdFieldValue == null)
						{
							return false;
						}
					}
				}
			}
			catch (NoSuchMethodException error)
			{
				logger.warn("No getter '%s' in '%s'", getterMethodName, targetClass.getName());
				
				continue;
			}
			catch (InvocationTargetException error)
			{
				logger.error("Invoking getter '%s' is failed.", getterMethodName, error);
				
				return false;
			}
			catch (IllegalAccessException error)
			{
				logger.error("Accessing getter '%s' is denied.", getterMethodName, error);
				
				return false;
			}
		}
		
		return true;
	}
}